/* Copyright (c) 2012 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 * $LastChangedRevision$
 */

#include "ble_flash.h"
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "nordic_common.h"
#include "nrf_error.h"
#include "nrf.h"
#include "nrf51_bitfields.h"
#include "ble_sdk_util.h"


#define BLE_FLASH_MAGIC_NUMBER 0x45DEAD55   /**< Magic value to identify if page contains valid data. */

static uint16_t s_flash_page_size;          /**< Size of one flash page. */
static uint8_t  s_elem_size;                /**< Size of the date elements to be written to the flash. */


/**@brief Erases a page in flash
 * 
 * @param[in]  p_page  Pointer to first word in page to be erased.
 */
static void flash_page_erase(uint32_t * p_page)
{
    // Turn on flash erase enable and wait until the NVMC is ready
    NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Een << NVMC_CONFIG_WEN_Pos);
    while (NRF_NVMC->READY == NVMC_READY_READY_Busy)
    {
        // Do nothing
    }

    // Erase page
    NRF_NVMC->ERASEPAGE = (uint32_t)p_page;
    while (NRF_NVMC->READY == NVMC_READY_READY_Busy)
    {
        // Do nothing
    }

    // Turn off flash erase enable and wait until the NVMC is ready
    NRF_NVMC->CONFIG &= ~(NVMC_CONFIG_WEN_Een << NVMC_CONFIG_WEN_Pos);
    while (NRF_NVMC->READY == NVMC_READY_READY_Busy)
    {
        // Do nothing
    }
}


/**@brief Fills a page in flash with a value.
 *
 * @param[in]  p_address  Pointer to first word in page to be filled.
 * @param[in]  value      Value to write to flash.
 */
static void flash_word_write(uint32_t * p_address, uint32_t value)
{
    // Turn on flash write enable and wait until the NVMC is ready
    NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos);
    while (NRF_NVMC->READY == NVMC_READY_READY_Busy)
    {
        // Do nothing
    }
    *p_address = value;

    // Turn off flash write enable and wait until the NVMC is ready
    NRF_NVMC->CONFIG = (NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos);
    while (NRF_NVMC->READY == NVMC_READY_READY_Busy)
    {
        // Do nothing
    }
}


void ble_flash_init(uint8_t elem_size)
{
    s_flash_page_size = (uint16_t)NRF_FICR->CODEPAGESIZE;
    s_elem_size       = elem_size;
}


void ble_flash_page_erase(uint8_t page_num)
{
    uint32_t * p_page = (uint32_t *)(s_flash_page_size * page_num);    
    flash_page_erase(p_page);
}


uint32_t ble_flash_write(uint8_t page_num, uint32_t * p_in_array, uint8_t elem_count)
{
    int        i;
    int        word_count = CEIL_DIV((uint16_t)elem_count * s_elem_size, sizeof(uint32_t));
    uint32_t * p_page;
    uint32_t * p_curr_addr;
    
    p_page      = (uint32_t *)(s_flash_page_size * page_num);
    p_curr_addr = p_page;

    // Erase flash page
    flash_page_erase(p_page);

    // Reserve space for magic number (for detecting if flash content is valid)
    p_curr_addr++;
    
    // Reserve space for saving elem_count
    p_curr_addr++;

    // Write data
    for (i = 0; i < word_count; i++)
    {
        flash_word_write(p_curr_addr, p_in_array[i]);
        p_curr_addr++;
    }
    
    // Write number of elements
    flash_word_write(p_page + 1, (uint32_t)(elem_count));
    
    // Write magic number to indicate that flash content is valid
    flash_word_write(p_page, BLE_FLASH_MAGIC_NUMBER);
    
    return NRF_SUCCESS;
}


uint32_t ble_flash_read(uint8_t page_num, uint32_t * p_out_array, uint8_t * p_elem_count)
{
    int        byte_count;
    uint32_t * p_page;
    uint32_t * p_curr_addr;
    
    p_page      = (uint32_t *)(s_flash_page_size * page_num);    
    p_curr_addr = p_page;
    
    // Check if block is valid
    if (*p_curr_addr != BLE_FLASH_MAGIC_NUMBER)
    {
        return NRF_ERROR_NOT_FOUND;
    }
    p_curr_addr++;

    // Read number of elements
    *p_elem_count = (uint8_t)(*(p_curr_addr));
    p_curr_addr++;
    
    // Read data
    byte_count = (*p_elem_count) * s_elem_size;
    memcpy(p_out_array, p_curr_addr, byte_count);
    
    return NRF_SUCCESS;
}
